import plotly.graph_objects as go
from plotly.subplots import make_subplots
import streamlit as st
from abc import ABC, abstractmethod
from src.services.theme_manager import ThemeManager
from src.services.interaction_service import InteractionService
import pandas as pd
import numpy as np
import plotly.express as px
from typing import List, Optional, Dict, Set, Any, Union, Type
from pandas import DataFrame
from pathlib import Path
import json
import uuid
import time
import logging
logger = logging.getLogger(__name__)

class LayoutConfig:
    def __init__(self):
        self.type = "vertical"
        self.row_heights = [0.7, 0.3]
        self.spacing = 0.1
        self.grid_columns = 2


class DataConfig:
    def __init__(self):
        self.primary_fields = ["close"]
        self.secondary_fields = ["volume"]
        self.field_aliases = {"close": "收盘价", "volume": "成交量"}

    def get_display_name(self, field):
        return self.field_aliases.get(field, field)


class ChartConfig:
    """可视化配置管理"""

    def __init__(self, theme_manager=None):
        from src.services.theme_manager import ThemeManager
        self.theme_manager = theme_manager or ThemeManager()
        self.theme = self.theme_manager.get_theme()  # 获取默认主题配置
        self.layout = LayoutConfig()
        self.data = DataConfig()
        self._config_manager = ChartConfigManager()
        
    def load(self):
        """显式加载配置文件"""
        config_data = self._config_manager.load_config()
        
        # 确保配置数据是字典格式
        if not isinstance(config_data, dict):
            st.error(f"配置数据格式错误: {type(config_data)}")
            config_data = self._config_manager._create_default_dict()
        
        # 更新配置
        theme_config = config_data.get("theme", {})
        layout_config = config_data.get("layout", {})
        data_config = config_data.get("data", {})
        
        # 更新主题配置
        if theme_config:
            self.theme = self.theme_manager.get_theme(theme_config.get("preset_name"))
        self.layout.__dict__.update(layout_config)
        self.data.__dict__.update(data_config)

    def save(self):
        """保存当前配置"""
        self._config_manager.save_config(self)


class ChartConfigManager:
    CONFIG_PATH = Path("src/support/config/chart_config.json")

    @classmethod
    def load_config(cls) -> dict:
        """加载配置并返回字典"""
        try:
            if cls.CONFIG_PATH.exists():
                with open(cls.CONFIG_PATH, "r", encoding="utf-8") as f:
                    return json.load(f)
        except Exception as e:
            st.error(f"配置加载失败: {str(e)}")
        
        # 返回默认配置
        return cls._create_default_dict()

    @classmethod
    def save_config(cls, config: ChartConfig):
        """保存配置到文件"""
        try:
            cls.CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
            save_data = {
                "theme": vars(config.theme),
                "layout": vars(config.layout),
                "data": vars(config.data),
            }
            with open(cls.CONFIG_PATH, "w", encoding="utf-8") as f:
                json.dump(save_data, f, ensure_ascii=False, indent=2)
        except Exception as e:
            st.error(f"配置保存失败: {str(e)}")

    @classmethod
    def _create_default_dict(cls) -> dict:
        """创建默认配置字典"""
        from src.services.theme_manager import ThemeManager
        theme_manager = ThemeManager()
        layout = LayoutConfig()
        data = DataConfig()
        
        return {
            "theme": theme_manager.get_theme(),
            "layout": vars(layout),
            "data": vars(data)
        }

    @classmethod
    def _migrate_old_config(cls, raw_data):
        """将旧版配置迁移到新结构"""
        return {
            "theme": {
                "mode": raw_data.get("current_theme", "dark"),
                "colors": raw_data.get("themes", {}),
            },
            "layout": {
                "type": raw_data.get("layout_type", "vertical"),
                "row_heights": raw_data.get("row_heights", [0.6, 0.4]),
            },
            "data": {
                "primary_fields": raw_data.get("primary_fields", []),
                "secondary_fields": raw_data.get("secondary_fields", []),
            },
        }
    @staticmethod
    def _get_default_config():

        default_config = {
            "main_chart": {
                "type": "K线图",       # 主图类型标识
                "fields": ["close"],  # 显示字段
                "data_source": "kline_data",  # 数据源标识
                "style": {            # 样式配置（参考网页4）
                    "line_width": 1.5,
                    "color": "#2c7be5"
                }
            },
            "sub_chart": {
                "show": True,         # 是否显示副图
                "type": "柱状图",      # 副图类型标识  
                "fields": ["volume"], # 显示字段
                "data_source": "trade_records", # 数据源标识
                "yaxis_name": "成交量", # Y轴标签
                "style": {
                    "type": "bar",    # 图形类型（bar/line）
                    "opacity": 0.6
                }
            },
            "version": "1.0"
        }
        return default_config
                    


class ChartBase(ABC):
    """图表基类"""
    figure: go.Figure
    logger: logging.Logger = logging.getLogger(__name__)
    default_line_width: float = 1.0
    main_color: str
    north_color: str
    default_up_color: str
    default_down_color: str
    
    def __init__(self, config: ChartConfig):
        self.config = config
        self.figure = go.Figure()
        self.main_color = "#4E79A7"  # 默认主色
        self.north_color = "#59A14F"  # 默认北向资金色
        self.default_up_color = "#25A776"  # 默认上涨色
        self.default_down_color = "#EF4444"  # 默认下跌色

    @abstractmethod
    def render(self, data: pd.DataFrame, scope: str) -> go.Figure:
        """渲染图表并返回Figure对象
        Args:
            data: 图表数据
            scope: 时间维度 (second/minute/hour/day/week/month/year)
        """
        pass

class Indicator(ABC):
    """指标基类"""
    @abstractmethod
    def apply(self, data: pd.DataFrame) -> List[Union[go.Scatter, go.Bar]]:
        """应用指标并返回trace列表"""
        pass

# 先定义所有具体指标类
class MAIndicator(Indicator):
    """均线指标"""
    def __init__(self, periods: List[int] = [5, 10, 20]):
        self.periods = periods
    
    def apply(self, data: pd.DataFrame) -> List[go.Scatter]:
        traces = []
        for period in self.periods:
            ma = data["close"].rolling(period).mean()
            traces.append(go.Scatter(
                x=data.index,
                y=ma,
                name=f"MA{period}",
                line=dict(width=1),
                opacity=0.7
            ))
        return traces

class MACDIndicator(Indicator):
    """MACD指标"""
    def __init__(self, fast=12, slow=26, signal=9):
        self.fast = fast
        self.slow = slow 
        self.signal = signal
    
    def apply(self, data: pd.DataFrame) -> List[Union[go.Scatter, go.Bar]]:
        exp1 = data["close"].ewm(span=self.fast, adjust=False).mean()
        exp2 = data["close"].ewm(span=self.slow, adjust=False).mean()
        macd = exp1 - exp2
        signal = macd.ewm(span=self.signal, adjust=False).mean()
        histogram = macd - signal
        
        return [
            go.Scatter(x=data.index, y=macd, name="MACD", line=dict(color="blue")),
            go.Scatter(x=data.index, y=signal, name="Signal", line=dict(color="orange")),
            go.Bar(x=data.index, y=histogram, name="Histogram", 
                  marker_color=np.where(histogram >= 0, "green", "red"))
        ]

class RSIIndicator(Indicator):
    """RSI指标"""
    def __init__(self, window=14):
        self.window = window
    
    def apply(self, data: pd.DataFrame) -> List[go.Scatter]:
        delta: pd.Series = data["close"].diff()
        gain: pd.Series = (delta.where(delta > 0, 0)).rolling(self.window).mean()
        loss: pd.Series = (-delta.where(delta < 0, 0)).rolling(self.window).mean()
        rs = gain / loss
        rsi = 100 - (100 / (1 + rs))
        
        return [
            go.Scatter(x=data.index, y=rsi, name=f"RSI{self.window}", line=dict(color="purple")),
            go.Scatter(x=data.index, y=[30]*len(data), name="超卖线", line=dict(color="red", dash="dash")),
            go.Scatter(x=data.index, y=[70]*len(data), name="超买线", line=dict(color="red", dash="dash"))
        ]


class IndicatorFactory:
    """指标工厂，统一管理指标创建"""
    _registry = {}  # 类型注册表: {"name": IndicatorClass}

    @classmethod
    def register(cls, name: str, indicator_class: Type[Indicator]):
        """注册指标类型"""
        if not issubclass(indicator_class, Indicator):
            raise TypeError(f"{indicator_class.__name__} must inherit from Indicator")
        cls._registry[name] = indicator_class

    @classmethod
    def create(cls, name: str, **params) -> Indicator:
        """创建指标实例"""
        if name not in cls._registry:
            raise ValueError(f"Unknown indicator: {name}. Available: {list(cls._registry.keys())}")
        return cls._registry[name](**params)

    @classmethod
    def get_available_indicators(cls) -> List[str]:
        """获取已注册的指标类型列表"""
        return list(cls._registry.keys())

# 注册内置指标类型
IndicatorFactory.register("ma", MAIndicator)
IndicatorFactory.register("macd", MACDIndicator)
IndicatorFactory.register("rsi", RSIIndicator)
# 移除重复的指标类定义

class IndicatorDecorator(ChartBase):
    """指标装饰器，支持工厂模式创建指标"""
    def __init__(self, chart: ChartBase, indicators: Union[List[Indicator], List[dict], str]):
        self._chart = chart   # 图表
        self._indicators = []  # 指标
        
        # 支持多种输入格式：
        # 1. 字典配置列表 - 通过工厂创建
        # 2. Indicator实例列表 - 直接使用
        # 3. 字符串列表 - 使用默认参数创建
        for item in indicators if isinstance(indicators, list) else [indicators]:
            if isinstance(item, dict):
                self._indicators.append(IndicatorFactory.create(**item))
            elif isinstance(item, Indicator):
                self._indicators.append(item)
            elif isinstance(item, str):
                self._indicators.append(IndicatorFactory.create(item))
            else:
                raise TypeError(f"Invalid indicator type: {type(item)}")
    
    def render(self, data: pd.DataFrame, scope: str) -> go.Figure:
        """渲染图表并添加指标"""
        fig = self._chart.render(data, scope)
        
        # 应用主题配置
        theme = self._chart.config.theme if hasattr(self._chart, 'config') else {}
        
        for indicator in self._indicators:
            for trace in indicator.apply(data):
                # 应用主题颜色
                if hasattr(trace, 'line') and trace.line and 'color' not in trace.line:
                    trace.line.color = theme.get("primary_color", "#2c7be5")
                fig.add_trace(trace)
        
        return fig


# 确保图表类定义在工厂类之前
class CapitalFlowChart(ChartBase):
    """资金流图表实现"""
    main_color: str
    north_color: str
    
    def __init__(self, config: ChartConfig):
        super().__init__(config)
        self.main_color = "#4E79A7"
        self.north_color = "#59A14F"

    def render(self, data: pd.DataFrame, scope: str = "day") -> go.Figure:
        """资金流图表渲染
        Args:
            data: 资金流数据
            scope: 时间维度 (day/week/month/year/minute)
        """
        from plotly.subplots import make_subplots
        self.figure = make_subplots(specs=[[{"secondary_y": True}]])
        self.figure.add_trace(
            go.Bar(
                x=data["date"],
                y=data["main_net"],
                name="主力净流入",
                marker_color=self.main_color,
                opacity=0.7,
            ),
            secondary_y=False,
        )
        self.figure.add_trace(
            go.Scatter(
                x=data["date"],
                y=data["north_net"].cumsum(),
                name="北向累计",
                line=dict(color=self.north_color, width=2),
                secondary_y=True,
            )
        )
        theme = self.config.theme
        self.figure.update_layout(
            plot_bgcolor=theme["background"],
            paper_bgcolor=theme["background"],
            barmode="relative",
            title="资金流向分析",
            title_font=dict(size=14, family=theme.get("font", "Arial")),
            legend=dict(font=dict(size=10, family=theme.get("font", "Arial"))),
        )
        return self.figure


class CandlestickChart(ChartBase):
    """K线图表实现"""
    def __init__(self, config: ChartConfig):
        super().__init__(config)

    def render(self, data: pd.DataFrame, scope: str) -> go.Figure:
        """K线图表渲染
        Args:
            data: K线数据
            scope: 时间维度 (second/minute/hour/day/week/month/year)
        """
        # 绘制K线
        self.figure.add_trace(
            go.Candlestick(
                x=data.index,
                open=data["open"],
                high=data["high"],
                low=data["low"],
                close=data["close"],
                name="K线",  # 设置trace名称
                increasing_line_color="#25A776",
                decreasing_line_color="#EF4444",
            )
        )
        

        # 显式禁用缩略图
        self.figure.update_layout(xaxis_rangeslider_visible=False)
        
        # 应用主题配置
        theme = self.config.theme
        layout = self.config.layout

        self.figure.update_layout(
            plot_bgcolor=theme["background"],
            paper_bgcolor=theme["background"],
            xaxis=dict(
                title="时间",
                tickvals=data.index[::100],
                ticktext=data["date" if scope in ("day", "week", "month", "year") else "time"][::100],
                tickangle=45,
                gridcolor=theme["grid"],
                title_font=dict(size=12, family=theme.get("font", "Arial")),
            ),
            yaxis=dict(
                gridcolor=theme["grid"],
                title_font=dict(size=12, family=theme.get("font", "Arial")),
            ),
            title_font=dict(size=14, family=theme.get("font", "Arial")),
            legend=dict(font=dict(size=10, family=theme.get("font", "Arial"))),
            margin=dict(t=30, b=30)  # 添加上下边距
        )
        return self.figure


class VolumeChart(ChartBase):
    """成交量图表实现"""
    default_up_color: str
    default_down_color: str
    
    def __init__(self, config: ChartConfig):
        super().__init__(config)
        self.default_up_color = "#25A776"
        self.default_down_color = "#EF4444"

    def render(self, data: pd.DataFrame, scope: str = "day") -> go.Figure:
        """成交量图表渲染
        Args:
            data: 成交量数据
            scope: 时间维度 (day/week/month/year/minute)
        """
        # 计算涨跌颜色
        colors = np.where(
            data["close"] >= data["open"],
            self.default_up_color,
            self.default_down_color,
        )

        # 绘制成交量
        self.figure.add_trace(
            go.Bar(x=data.index, y=data["volume"], marker_color=colors, name="成交量")
        )

        # 应用主题配置
        theme = self.config.theme
        layout = self.config.layout

        self.figure.update_layout(
            title="成交量",
            plot_bgcolor=theme["background"],
            paper_bgcolor=theme["background"],
            xaxis=dict(
                
                title="时间",
                tickvals=data.index[::100],
                ticktext=data["date" if scope in ("day", "week", "month", "year") else "time"][::100],
                tickangle=45,
                gridcolor=theme["grid"],
                title_font=dict(size=12, family=theme.get("font", "Arial")),
            ),
            yaxis=dict(
                gridcolor=theme["grid"],
                title_font=dict(size=12, family=theme.get("font", "Arial")),
            ),
            title_font=dict(size=14, family=theme.get("font", "Arial")),
            legend=dict(font=dict(size=10, family=theme.get("font", "Arial"))),
        )
        return self.figure


class ChartFactory:
    """图表工厂类，支持动态注册和创建图表实例"""
    
    _chart_types = {}  # 存储注册的图表类型
    
    @classmethod
    def register_chart(cls, chart_type: str, chart_class):
        """注册新的图表类型
        
        Args:
            chart_type: 图表类型标识字符串
            chart_class: 图表实现类(必须继承自ChartBase)
        """
        if not issubclass(chart_class, ChartBase):
            raise TypeError(f"{chart_class.__name__} 必须继承自 ChartBase")
        cls._chart_types[chart_type] = chart_class
    
    @classmethod
    def create_chart(cls, chart_type: str, config: ChartConfig) -> ChartBase:
        """创建图表实例
        
        Args:
            chart_type: 图表类型标识字符串
            config: 图表配置对象
            
        Returns:
            图表实例
            
        Raises:
            ValueError: 如果图表类型未注册
        """
        if chart_type not in cls._chart_types:
            raise ValueError(f"未知的图表类型: {chart_type}. 可用类型: {list(cls._chart_types.keys())}")
        return cls._chart_types[chart_type](config)
    
    @classmethod
    def get_registered_charts(cls) -> list:
        """获取已注册的图表类型列表"""
        return list(cls._chart_types.keys())


# 注册内置图表类型
ChartFactory.register_chart("capital_flow", CapitalFlowChart)
ChartFactory.register_chart("candlestick", CandlestickChart)
ChartFactory.register_chart("volume", VolumeChart)






class CombinedChartConfig(ChartConfig):
    def __init__(self):
        super().__init__()
        self.layout_type = st.sidebar.selectbox(
            "布局方式", options=["垂直堆叠", "网格排列"], index=0
        )
        self.row_heights = [0.6, 0.4]  # 默认K线+成交量高度比例
        self.vertical_spacing = 0.05


class DataBundle:
    """数据容器，用于存储多种类型的数据"""
    kline_data: Optional[DataFrame]
    trade_records: Optional[DataFrame]
    capital_flow: Optional[DataFrame]

    def __init__(
        self,
        raw_data: Optional[DataFrame] = None,
        transaction_data: Optional[DataFrame] = None,
        capital_flow_data: Optional[DataFrame] = None,
    ):
        self.kline_data = raw_data
        self.trade_records = transaction_data
        self.capital_flow = capital_flow_data
        self.kline_data = raw_data  # K线数据
        self.trade_records = transaction_data  # 交易记录
        self.capital_flow = capital_flow_data  # 新增资金流数据字段

    def get_all_columns(self) -> List[str]:
        """获取所有列名"""
        columns = set()
        # 遍历所有数据容器字段
        for attr in ["kline_data", "trade_records", "capital_flow"]:
            df = getattr(self, attr)
            if df is not None and isinstance(df, DataFrame):
                columns.update(df.columns.tolist())
        return list(columns)  # 转换为列表返回


class ChartService:
    """图表服务，支持多种数据源的图表绘制"""
    logger: logging.Logger = logging.getLogger(__name__)
    default_line_width: float = 1.0
    data_bundle: DataBundle
    figure: go.Figure
    _selected_primary_fields: List[str]
    _selected_secondary_fields: List[str]
    _chart_types: Dict[str, str]

    def __init__(self, data_bundle: DataBundle):
        self.data_bundle = data_bundle
        self._interaction_service = None
        self.figure = go.Figure()
        self._selected_primary_fields = []
        self._selected_secondary_fields = []
        self._chart_types = {"primary": "K线图", "secondary": "柱形图"}
        self.index = None  # 初始化index属性
        self._config = None  # 初始化配置属性

    def _get_interaction_service(self):
        """惰性获取InteractionService实例"""
        if self._interaction_service is None:
            self._interaction_service = InteractionService()
        return self._interaction_service

    @staticmethod
    def get_chart_service(data_bundle: DataBundle, _strategy_id: Optional[str] = None) -> 'ChartService':
        """基于策略ID的缓存实例工厂
        
        Args:
            data_bundle: 必需的数据容器对象
            _strategy_id: 可选策略ID，用于缓存隔离
            
        Returns:
            ChartService实例
            
        Raises:
            ValueError: 如果data_bundle为None
        """
        if data_bundle is None:
            raise ValueError("data_bundle参数不能为None")
        return ChartService(data_bundle)

    def _handle_config_change(self, *args):
        """处理配置变更的回调函数"""
        if len(args) == 0:
            return
            
        # 解析参数 - Streamlit会传递3个参数: widget_key, value, field_type
        if len(args) >= 3:
            _, _, field_type = args
        elif len(args) >= 2:
            _, field_type = args
        else:
            field_type = args[0]

        # 防抖机制：如果距离上次变更时间小于300ms则忽略
        current_time = time.time()
        if current_time - st.session_state.get("last_change", 0) < 0.3:
            return
        st.session_state["last_change"] = current_time

        # 获取配置key
        config_key = f"chart_config_{st.session_state.chart_instance_id}"

        # 获取新值
        new_value = st.session_state[f"{st.session_state.strategy_id}_{field_type}"]

        # 更新配置
        if field_type in ["main_type", "main_fields"]:
            st.session_state[config_key]["main_chart"].update(
                {field_type.split("_")[1]: new_value}
            )
        elif field_type in ["sub_type", "sub_fields", "show_sub"]:
            key_map = {
                "sub_type": "type",
                "sub_fields": "fields", 
                "show_sub": "show"
            }
            st.session_state[config_key]["sub_chart"].update(
                {key_map[field_type]: new_value}
            )

        # 设置重绘标志
        st.session_state["need_redraw"] = True

    def _refresh_chart(self, config: dict):
        """根据配置刷新图表"""
        # 更新主图类型
        self._chart_types["primary"] = config["main_chart"]["type"]
        # 更新副图类型
        self._chart_types["secondary"] = config["sub_chart"]["type"]
        # 更新主图字段
        self._selected_primary_fields = config["main_chart"]["fields"]
        # 更新副图字段
        self._selected_secondary_fields = config["sub_chart"]["fields"]

    def render_chart_controls(self) -> go.Figure:
        """渲染作图配置"""
        # 初始化配置
        if not hasattr(st.session_state, 'chart_instance_id'):
            st.session_state.chart_instance_id = str(uuid.uuid4())
            
        # 生成配置key
        config_key = f"chart_config_{st.session_state.chart_instance_id}"

        # 初始化配置
        if config_key not in st.session_state:
            st.session_state[config_key] = ChartConfigManager._get_default_config()
            
        # 确保策略ID存在
        if not hasattr(st.session_state, 'strategy_id'):
            st.session_state.strategy_id = "default_strategy"


        # 新配置new_config初始化
        if "new_config" not in st.session_state:
            st.session_state.new_config = ChartConfigManager._get_default_config()  # 初始化默认配置
        if "config_key" not in st.session_state:
            st.session_state.config_key = ChartConfigManager._get_default_config()  # 初始化默认配置
             

        # # 片段级状态初始化
        # fragment_id = f"chart_fragment_{uuid.uuid4().hex[:8]}"
        # fragment_state = {
        #     "main_chart": {"type": "K线图", "fields": ["close"]},
        #     "sub_chart": {"show": True, "type": "柱状图", "fields": ["volume"]},
        #     "expander_expanded": True,
        #     "version": 1,
        # }




        # 渲染主图配置
        @st.fragment
        def render_main_chart_config():
            """渲染主图配置选项"""
            col1, col2 = st.columns(2)
            with col1:
                new_type = st.selectbox(
                    "主图类型",
                    options=["折线图", "K线图", "面积图"],
                    key=f"{st.session_state.strategy_id}_main_type",
                    index=["折线图", "K线图", "面积图"].index(
                        st.session_state.config_key["main_chart"]["type"]
                    ),
                    on_change=self._handle_config_change,
                    args=(config_key, "main_type"),
                )
            with col2:
                available_fields = self.data_bundle.get_all_columns()
                if new_type == "K线图":
                    required_fields = {"open", "low", "high", "close"}
                    current_fields = set(st.session_state.config_key["main_chart"]["fields"])

                    # 强制合并必选字段（不允许用户删除）
                    final_fields = list(current_fields.union(required_fields))

                    # 渲染多选框（自动选中必选字段）
                    new_fields = st.multiselect(
                        "主图字段",
                        options=available_fields,
                        default=final_fields,
                        key=f"{st.session_state.strategy_id}_main_fields",
                        on_change=self._handle_config_change,
                        args=(st.session_state.config_key, "main_fields"),
                    )

                    # 关键：强制回写必选字段，防止用户删除
                    st.session_state.config_key["main_chart"]["fields"] = list(set(new_fields).union(required_fields))
                else:
                    new_fields = st.multiselect(
                        "主图字段",
                        options=available_fields,
                        default=st.session_state.config_key["main_chart"]["fields"],
                        key=f"{st.session_state.strategy_id}_main_fields",
                        on_change=self._handle_config_change,
                        args=(st.session_state.config_key, "main_fields"),
                    )
            
            st.session_state.new_config["main_chart"]["type"] = new_type
            st.session_state.new_config["main_chart"]["fields"] = new_fields

        # 渲染副图配置
        @st.fragment
        def render_sub_chart_config():
            """渲染副图配置选项"""
            # 初始化默认值
            new_sub_type = "柱状图"
            new_sub_fields = ["volume"]
            
            show_sub = st.checkbox(
                "显示副图",
                value=st.session_state.config_key["sub_chart"]["show"],
                key=f"{st.session_state.strategy_id}_show_sub",
                on_change=self._handle_config_change,
                args=(st.session_state.config_key, "show_sub"),
            )

            if st.session_state.config_key["sub_chart"]["show"]:
                col3, col4 = st.columns(2)
                with col3:
                    new_sub_type = st.selectbox(
                        "副图类型",
                        options=["柱状图", "折线图", "MACD"],
                        key=f"{st.session_state.strategy_id}_sub_type",
                        index=["柱状图", "折线图", "MACD"].index(
                            st.session_state.config_key["sub_chart"]["type"]
                        ),
                        on_change=self._handle_config_change,
                        args=(st.session_state.config_key, "sub_type"),
                    )
                with col4:
                    available_fields = self.data_bundle.get_all_columns()
                    new_sub_fields = st.multiselect(
                        "副图字段",
                        options=available_fields,
                        default=st.session_state.config_key["sub_chart"]["fields"],
                        key=f"{st.session_state.strategy_id}_sub_fields",
                        on_change=self._handle_config_change,
                        args=(st.session_state.config_key, "sub_fields"),
                    )
            st.session_state.new_config["sub_chart"]["type"] = new_sub_type
            st.session_state.new_config["sub_chart"]["fields"] = new_sub_fields

        # 渲染保存和重置按钮，作图
        @st.fragment
        def render_save_and_reset_buttons():
            """渲染保存和重置按钮"""
            col5, col6 = st.columns(2)
            with col5:
                if st.button("💾 保存配置", key=f"save_{config_key}"):

                    # 直接使用session_state的最新值
                    st.session_state[config_key].update(st.session_state.new_config)  # 更新保存的配置
                    self.logger.debug(f"作图配置已保存：{st.session_state.config_key}")
                    st.session_state["need_redraw"] = True

                    # 使用更轻量的通知方式
                    st.toast("✅ 配置已保存", icon="💾")
                    

            with col6:
                if st.button("🔄 重置", key=f"reset_{config_key}"):
                    st.session_state[config_key].update(ChartConfigManager._get_default_config())
                    
                    st.toast("⚡ 配置已重置", icon="🔄")
                    self.logger.debug(f"作图配置已重置：{st.session_state[config_key]}")
                    st.session_state.need_redraw = True

        # 执行渲染
        with st.expander("📊 图表配置", expanded=True):  # 确保默认展开
            render_main_chart_config()
            render_sub_chart_config()
            render_save_and_reset_buttons()

        with st.expander("会话状态监控"):
            st.write(st.session_state)


        # 版本驱动更新
        if st.session_state.get("config_version") != st.session_state.config_key["version"]:
            self._refresh_chart(st.session_state.config_key)
            st.session_state.config_version = st.session_state.config_key["version"]

        return self.figure

    @st.fragment
    def render_chart_button(self, config: dict):
        """渲染作图按钮"""

           
        
        self.logger.debug(set(config["main_chart"]["fields"]))
        
        if st.button("显示回测曲线", key="draw_backtest"):
            ma_traces = self.render_ma()
            REQUIRED_KLINE_FIELDS = {"open", "low", "high", "close"}
            if (
                config["main_chart"]["type"] == "K线图"
                and not REQUIRED_KLINE_FIELDS.issubset(set(config["main_chart"]["fields"]))
            ):
                # 显示错误提示（支持 3 种方式）
                st.toast(":red[错误] K线图必须包含 open/low/high/close 字段", icon="🔥")  # 轻量提示
            else:
                # 确保配置已固化到会话状态
                if "config_key" not in st.session_state:
                    st.session_state.config_key = ChartConfigManager._get_default_config()  # 初始化默认配置
                
                # 生成图表
                fig = self.create_combined_chart(config)
                for trace in ma_traces:
                  fig.add_trace(trace)
                st.write(fig)

    def create_interactive_chart(self) -> go.Figure:
        """生成交互式配置的图表"""
        if not hasattr(self, 'figure'):
            self.figure = go.Figure()
        # 参数有效性检查
        if not self._selected_primary_fields:
            raise ValueError("至少需要选择一个主图字段")

        # 创建基础图表
        fig = self.create_combined_chart(
            primary_cols=self._selected_primary_fields,
            secondary_cols=(
                self._selected_secondary_fields
                if self._selected_secondary_fields
                else None
            ),
        )

        return fig

    def create_kline(self, scope: str = "day", auto_listen: bool = False) -> go.Figure:
        """创建K线图(通过工厂模式)
        Args:
            scope: 时间维度 (day/week/month/year/minute)
            auto_listen: 是否自动注册交互事件监听，默认为False
        """
        # logging
        logger.debug(f"开始创建K线图，数据形状: {self.data_bundle.kline_data.shape if self.data_bundle.kline_data is not None else 'None'}",
                   extra={'connection_id': str(id(self))})
        if self.data_bundle.kline_data is None:
            logger.error("创建K线图失败: 缺少K线数据",
                       extra={'connection_id': str(id(self))})
            raise ValueError("缺少K线数据")

        # 通过工厂创建图表实例
        config = ChartConfig()
        kline = ChartFactory.create_chart("candlestick", config)
        
        # 添加指标装饰
        decorated_chart = IndicatorDecorator(
            kline,
            indicators=[MAIndicator([5, 10, 20])]
        )
        
        fig = decorated_chart.render(self.data_bundle.kline_data, scope)
        self.figure = fig
        
        # 只有auto_listen为True时才注册事件监听
        if auto_listen:
            self._get_interaction_service().subscribe(
                lambda x_range: fig.update_xaxes(range=x_range))
            
        logger.debug("K线图创建完成",
                    extra={'connection_id': str(id(self))})
        return fig

    def create_volume_chart(self, auto_listen: bool = False) -> go.Figure:
        """创建成交量图(通过工厂模式)
        Args:
            auto_listen: 是否自动注册交互事件监听，默认为False
        """
        if self.data_bundle.kline_data is None:
            raise ValueError("缺少K线数据")

        config = ChartConfig()
        volume = ChartFactory.create_chart("volume", config)
        fig = volume.render(self.data_bundle.kline_data)
        
        # 只有auto_listen为True时才注册事件监听
        if auto_listen:
            self._get_interaction_service().subscribe(
                lambda x_range: fig.update_xaxes(range=x_range))
            
        return fig

    def create_capital_flow_chart(self, config: Optional[dict] = None) -> go.Figure:
        """创建资金流图表(通过工厂模式)"""
        if self.data_bundle.capital_flow is None:
            raise ValueError("缺少资金流数据")

        # 通过工厂创建图表实例
        flow_config = ChartConfig()
        capital_chart = ChartFactory.create_chart("capital_flow", flow_config)

        # 应用自定义配置 - 直接使用配置值
        if config:
            capital_chart.main_color = config.get("main_color", "#4E79A7")
            capital_chart.north_color = config.get("north_color", "#59A14F")
        else:
            # 使用默认值
            capital_chart.main_color = "#4E79A7"
            capital_chart.north_color = "#59A14F"

        return capital_chart.render(self.data_bundle.capital_flow)


    def create_traces(self, config, data_source, is_secondary=False):
        """动态生成trace的工厂函数"""
        trace_type_map = {
            'K线图': go.Candlestick,
            '折线图': go.Scatter,
            '柱状图': go.Bar
        }
        
        traces = []
        # graph_type = trace_type_map[config.get('type', '折线图')]
        style = config.get('style', {})
        
        count = 0
        if config.get('type', '折线图') == 'K线图':
            count = count + 1
            # self.logger.debug(f"正在作图trace_{count},graph_type = {graph_type}, fields = {config['fields']}")
            trace = self.drawCandlestick(data_source)
            traces.append((trace, is_secondary))
            self.logger.debug(f"my_trace is {trace}, my is_sec is {is_secondary}")
        else:
            for field in config['fields']: # 
                count = count + 1
                # self.logger.debug(f"正在作图trace_{count},graph_type = {graph_type}, fields = {config['fields']}")
                
                trace = go.Bar(
                    x=data_source.index,
                    y=data_source[field],
                    name=f"{config['type']}-{field}",
                    marker=dict(
                        opacity=style.get('opacity', 0.6),
                        color=style.get('color', '#ff7f0e')
                    )
                )
                traces.append((trace, is_secondary))
                self.logger.debug(f"my_trace is {trace}, my is_sec is {is_secondary}")
        return traces


    def create_combined_chart(
        self,
        config: dict,
        primary_cols: Optional[List[str]] = None,
        secondary_cols: Optional[List[str]] = None
    ) -> go.Figure:
        """
        创建支持单/双Y轴的组合图表

        Parameters:
        -----------
        config : dict
            图表配置字典，结构示例：
            {
                "main_chart": {
                    "type": "K线图",       # 主图类型标识
                    "fields": ["close"],  # 显示字段
                    "data_source": "kline_data",  # 数据源标识
                    "style": {            # 样式配置（参考网页4）
                        "line_width": 1.5,
                        "color": "#2c7be5"
                    }
                },
                "sub_chart": {
                    "show": True,         # 是否显示副图
                    "type": "成交量",      # 副图类型标识  
                    "fields": ["volume"], # 显示字段
                    "data_source": "trade_records", # 数据源标识
                    "yaxis_name": "成交量", # Y轴标签
                    "style": {
                        "type": "bar",    # 图形类型（bar/line）
                        "opacity": 0.6
                    }
                }
            }

        sub_chart.style.type = 'bar'  --- 柱状图            
        'scatter'  --- 折线图
        
        Returns:
        --------
        go.Figure
            配置好的Plotly图表对象

        Examples:
        ---------
        >>> # 单Y轴调用
        >>> fig = create_combined_chart(df, ['close', 'MA20'])

        >>> # 双Y轴调用
        >>> fig = create_combined_chart(df, ['close'], ['volume'], "成交量")
        """
        from plotly.subplots import make_subplots

        fig = make_subplots(shared_xaxes=True, specs=[[{"secondary_y": config['sub_chart'].get('show', True)}]])
        
        # st.write(self.data_bundle.kline_data.index) # debug
        # 动态处理主副图配置
        chart_configs = [
            (config['main_chart'], self.data_bundle.kline_data, False),
            (config['sub_chart'], self.data_bundle.kline_data, True)
            if config['sub_chart'].get('show') else (None, None, None)
        ]
        
        for cfg, data, secondary in chart_configs:
            if cfg and data is not None: # 有参数，有数据
                for trace, is_secondary in self.create_traces(cfg, data, secondary):
                    fig.add_trace(trace, secondary_y=is_secondary)
                    self.logger.debug(f"已添加{trace}")
        
        fig.update_layout(
            hovermode='x unified',
            legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
            xaxis=dict(
                title="时间",
                tickvals=self.data_bundle.kline_data.index[::33],
                ticktext=self.data_bundle.kline_data["date"][::33],
                tickangle=45,
            ),
            yaxis1=dict(
                fixedrange=False, # 不固定Y轴范围
                
                title="price"), # bug:需要调整
            
            yaxis2=dict(
                fixedrange=False, # 不固定Y轴范围
                showgrid=config['sub_chart'].get('show', True),
                title=config['sub_chart'].get('yaxis_name', 'Secondary Y'),
                visible=config['sub_chart'].get('show', True)
            ) if config['sub_chart'].get('show') else {}
        )
        
        return fig

    def draw_equity_and_allocation(self, equity_data: Optional[pd.DataFrame] = None) -> None:
        """绘制净值与资产配置比例单轴图表

        Args:
            equity_data: 净值数据DataFrame，包含timestamp, total_value, positions_value列
        """
        
        # 数据准备：使用传入数据或默认数据源
        data = equity_data if equity_data is not None else self.data_bundle.trade_records
        
        # 数据验证和预处理
        if data is None or data.empty:
            raise ValueError("缺少净值数据")
        
        required_cols = {'timestamp', 'total_value', 'positions_value'}
        if not required_cols.issubset(data.columns):
            raise ValueError(f"净值数据缺少必要列，需要: {required_cols}")
        
        # 计算净值百分比变化（相对于初始值）
        initial_value = data['total_value'].iloc[0]
        data = data.copy()
        data['return_pct'] = ((data['total_value'] - initial_value) / initial_value) * 100
        
        # 计算资产配置比例 (持仓市值 / 总资产 × 100%)
        data['allocation_pct'] = (data['positions_value'] / data['total_value']) * 100
        
        # 创建单轴图表
        fig = go.Figure()
        
        # 净值百分比变化
        fig.add_trace(
            go.Scatter(
                x=data['timestamp'],
                y=data['return_pct'],
                name="净值变化 (%)",
                line=dict(color="#1f77b4", width=2),
                hovertemplate="%{x}<br>净值: %{y:.2f}%<extra></extra>"
            )
        )
        
        # 资产配置比例
        fig.add_trace(
            go.Scatter(
                x=data['timestamp'],
                y=data['allocation_pct'],
                name="资产配置比例 (%)",
                line=dict(color="#ff7f0e", width=2),
                hovertemplate="%{x}<br>配置比例: %{y:.2f}%<extra></extra>"
            )
        )
        
        # 配置图表布局
        fig.update_layout(
            title="📊 净值与资产配置分析",
            xaxis_title="时间",
            yaxis_title="百分比 (%)",
            hovermode="x unified",
            legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
            height=500
        )
        
        # 添加30%激进策略警戒线
        fig.add_hline(y=30, line_dash="dash", line_color="red", 
                     annotation_text="激进策略警戒线 (30%)", 
                     annotation_position="bottom right")
        
        # 添加零线参考
        fig.add_hline(y=0, line_dash="dash", line_color="green",
                     annotation_text="盈亏平衡线")

        # 显示图表
        st.plotly_chart(fig, use_container_width=True)

    def drawMA(self, data: Optional[DataFrame], periods: List[int]) -> List[go.Scatter]:
        """绘制均线"""
        traces = []
        if data is not None and "close" in data.columns:
            for period in periods:
                ma = data["close"].rolling(window=period).mean()
                traces.append(
                    go.Scatter(
                        x=data.index,
                        y=ma,
                        name=f"MA{period}",
                        line=dict(width=1),
                    )
                )
        return traces

    def drawMACD(self, data: pd.DataFrame, fast=12, slow=26, signal=9):
        """绘制MACD指标"""
        exp1 = data["close"].ewm(span=fast, adjust=False).mean()
        exp2 = data["close"].ewm(span=slow, adjust=False).mean()
        macd = exp1 - exp2
        signal_line = macd.ewm(span=signal, adjust=False).mean()
        histogram = macd - signal_line

        fig = go.Figure()
        fig.add_trace(
            go.Scatter(
                x=data.index,
                y=macd,
                name="MACD",
                line=dict(color="blue", width=self.default_line_width),
            )
        )
        fig.add_trace(
            go.Scatter(
                x=data.index,
                y=signal_line,
                name="Signal",
                line=dict(color="orange", width=self.default_line_width),
            )
        )
        fig.add_trace(
            go.Bar(
                x=data.index,
                y=histogram,
                name="Histogram",
                marker_color=np.where(histogram >= 0, "green", "red"),
            )
        )
        fig.update_layout(
            title="MACD", xaxis_title="时间", yaxis_title="MACD", template="plotly_dark"
        )
        st.plotly_chart(fig)

    def drawBollingerBands(self, data, window=20, num_std=2):
        """绘制布林带"""
        fig = go.Figure()
        rolling_mean = data["close"].rolling(window=window).mean()
        rolling_std = data["close"].rolling(window=window).std()
        upper_band = rolling_mean + (rolling_std * num_std)
        lower_band = rolling_mean - (rolling_std * num_std)

        fig.add_trace(
            go.Scatter(
                x=data.index,
                y=data["close"],
                name="价格",
                line=dict(color="white", width=self.default_line_width),
            )
        )
        fig.add_trace(
            go.Scatter(
                x=data.index,
                y=upper_band,
                name="上轨",
                line=dict(color="red", width=self.default_line_width),
            )
        )
        fig.add_trace(
            go.Scatter(
                x=data.index,
                y=rolling_mean,
                name="中轨",
                line=dict(color="blue", width=self.default_line_width),
            )
        )
        fig.add_trace(
            go.Scatter(
                x=data.index,
                y=lower_band,
                name="下轨",
                line=dict(color="green", width=self.default_line_width),
            )
        )
        fig.update_layout(
            title="布林带",
            xaxis_title="时间",
            yaxis_title="价格",
            template="plotly_dark",
        )
        st.plotly_chart(fig)

    def drawVolume(self, data):
        """绘制成交量图"""
        fig = go.Figure()
        colors = np.where(data["close"] >= data["open"], "green", "red")
        fig.add_trace(
            go.Bar(
                x=data.index,
                y=data["volume"],
                name="成交量",
                marker_color=colors,
            )
        )
        fig.update_layout(
            title="成交量",
            xaxis_title="时间",
            yaxis_title="成交量",
            template="plotly_dark",
        )
        st.plotly_chart(fig)

    def drawCandlestick(self, data):
        """绘制K线图"""
        # 初始化主题
        current_theme = self._select_theme()

        
        trace = go.Candlestick(
            x=data['date'].index,
            open=data["open"],
            high=data["high"],
            low=data["low"],
            close=data["close"],
            name="K线",
        )

        return trace

    def _select_theme(self):
        """主题选择组件"""

        theme_manager = ThemeManager()
        return st.selectbox(
            "主题模式", options=list(theme_manager.themes.keys()), index=0
        )

    def render_ma(self):
        """均线组件"""
        # 初始化会话状态
        if "show_ma" not in st.session_state:
            st.session_state.show_ma = True
        if "ma_periods" not in st.session_state:
            st.session_state.ma_periods = [5, 10, 20]

        # 回调函数避免不必要的 rerun  # bug:未解决rerun
        def update_ma_state():
            st.session_state.show_ma = st.session_state["show_ma_checkbox"]
            st.session_state.ma_periods = st.session_state["ma_periods_select"]

        # 均线组件
        st.checkbox(
            "显示均线",
            value=st.session_state.show_ma,
            key="show_ma_checkbox",
            on_change=update_ma_state,
        )

        st.multiselect(
            "均线周期",
            options=[5, 10, 20, 30, 60],
            default=st.session_state.ma_periods,
            key="ma_periods_select",
            on_change=update_ma_state,
        )

        ma_traces = []
        if st.session_state.show_ma:
            ma_traces = self.drawMA(self.data_bundle.kline_data, st.session_state.ma_periods)

        if not ma_traces and st.session_state.show_ma:
            st.warning("请至少选择一个均线周期")
        return ma_traces


    def drawRSI(self, data, window=14):
        """绘制相对强弱指数(RSI)"""
        fig = go.Figure()
        delta = data["close"].diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
        rs = gain / loss
        rsi = 100 - (100 / (1 + rs))
        fig.add_trace(
            go.Scatter(
                x=data.index,
                y=rsi,
                name="RSI",
                line=dict(color="blue", width=self.default_line_width),
            )
        )
        fig.add_hline(y=30, line_dash="dash", line_color="red")
        fig.add_hline(y=70, line_dash="dash", line_color="red")
        fig.update_layout(
            title="相对强弱指数(RSI)",
            xaxis_title="时间",
            yaxis_title="RSI",
            template="plotly_dark",
        )
        st.plotly_chart(fig)

    def drawallRSI(data, window, color, line_width):
        """绘制所有RSI"""
        fig_rsi = go.Figure()
        fig_rsi.add_trace(
            go.Scatter(
                x=data.index,
                y=data[f"{window}RSI"],
                yaxis="y1",
                mode="lines",
                line=dict(color=color, width=line_width),
                name=f"{window}RSI",
                hovertext=data["time"],
                showlegend=True,
            )
        )
        fig_rsi.add_hline(
            y=30,
            line_dash="dash",
            line_color="white",
            annotation_text="y=30",
            annotation_position="top left",
        )
        fig_rsi.add_hline(
            y=70,
            line_dash="dash",
            line_color="white",
            annotation_text="y=70",
            annotation_position="top left",
        )
        fig_rsi.update_layout(
            title=f"{window}RSI",
            xaxis=dict(
                gridcolor="white",
                title="时间",
                tickvals=data.index[::1000],
                ticktext=data["time"][::1000],
            ),
            yaxis=dict(
                gridcolor="white",
                title=f"{window}RSI",
                titlefont=dict(color="white"),
                tickfont=dict(color="white"),
                tickvals=[30, 70],
                ticktext=["30", "70"],
            ),
            template="plotly",
            legend=dict(x=0.1, y=1.1),
            hovermode="x unified",
        )
        st.plotly_chart(fig_rsi)

    
    def create_fund_flow_chart(self, fund_flow_data: pd.DataFrame) -> go.Figure:
        """创建资金流向图表"""
        fig = px.line(
            fund_flow_data,
            x="date",
            y=[
                "main_net_inflow_amt",
                "super_large_net_inflow_amt",
                "large_net_inflow_amt",
                "mid_net_inflow_amt",
                "retail_net_inflow_amt",
            ],
            labels={"value": "资金流向 (亿)", "date": "日期", "variable": "资金类型"},
            title="大盘资金流向分析",
        )
        fig.update_layout(
            legend_title_text="资金类型",
            xaxis_title="日期",
            yaxis_title="资金流向 (亿)",
        )
        return fig

    def draw_drawdown_analysis(self, equity_data: pd.DataFrame) -> None:
        """绘制回撤分析图表

        Args:
            equity_data: 净值数据DataFrame，包含timestamp和total_value列
        """
        if equity_data is None or equity_data.empty:
            st.warning("无净值数据可用于回撤分析")
            return

        # 计算回撤
        equity_data = equity_data.copy()
        equity_data['peak'] = equity_data['total_value'].cummax()
        equity_data['drawdown'] = (equity_data['total_value'] - equity_data['peak']) / equity_data['peak'] * 100

        # 创建图表
        fig = go.Figure()

        # 添加回撤曲线
        fig.add_trace(
            go.Scatter(
                x=equity_data['timestamp'],
                y=equity_data['drawdown'],
                name="回撤 (%)",
                line=dict(color="red", width=2),
                fill='tozeroy',
                fillcolor='rgba(255,0,0,0.2)'
            )
        )

        # 配置图表
        fig.update_layout(
            title="回撤分析",
            xaxis_title="时间",
            yaxis_title="回撤 (%)",
            template="plotly_white",
            height=400
        )

        st.plotly_chart(fig, use_container_width=True)

    def draw_returns_distribution(self, equity_data: pd.DataFrame) -> None:
        """绘制收益分布图表

        Args:
            equity_data: 净值数据DataFrame，包含timestamp和total_value列
        """
        if equity_data is None or equity_data.empty:
            st.warning("无净值数据可用于收益分布分析")
            return

        # 计算日收益率
        equity_data = equity_data.copy()
        equity_data['daily_return'] = equity_data['total_value'].pct_change() * 100

        # 创建直方图
        fig = px.histogram(
            equity_data,
            x='daily_return',
            title="日收益率分布",
            nbins=50,
            labels={'daily_return': '日收益率 (%)'}
        )

        # 添加统计信息
        mean_return = equity_data['daily_return'].mean()
        std_return = equity_data['daily_return'].std()

        fig.add_vline(x=mean_return, line_dash="dash", line_color="red",
                     annotation_text=f"均值: {mean_return:.2f}%")
        fig.add_vline(x=mean_return + std_return, line_dash="dash", line_color="orange",
                     annotation_text=f"+1σ: {mean_return + std_return:.2f}%")
        fig.add_vline(x=mean_return - std_return, line_dash="dash", line_color="orange",
                     annotation_text=f"-1σ: {mean_return - std_return:.2f}%")

        fig.update_layout(height=400)
        st.plotly_chart(fig, use_container_width=True)

    def draw_trading_signals(self, price_data: pd.DataFrame, signals_data: pd.DataFrame) -> None:
        """绘制交易信号图表

        Args:
            price_data: 价格数据DataFrame
            signals_data: 信号数据DataFrame
        """
        if price_data is None or price_data.empty:
            st.warning("无价格数据可用于信号分析")
            return

        # 创建K线图
        fig = go.Figure()

        # 添加K线
        fig.add_trace(
            go.Candlestick(
                x=price_data.index,
                open=price_data['open'],
                high=price_data['high'],
                low=price_data['low'],
                close=price_data['close'],
                name="K线"
            )
        )

        # 如果有信号数据，添加交易信号标记
        if signals_data is not None and not signals_data.empty:
            buy_signals = signals_data[signals_data['signal'] == 'BUY']
            sell_signals = signals_data[signals_data['signal'] == 'SELL']

            if not buy_signals.empty:
                fig.add_trace(
                    go.Scatter(
                        x=buy_signals.index,
                        y=buy_signals['price'],
                        mode='markers',
                        marker=dict(symbol='triangle-up', size=10, color='green'),
                        name='买入信号'
                    )
                )

            if not sell_signals.empty:
                fig.add_trace(
                    go.Scatter(
                        x=sell_signals.index,
                        y=sell_signals['price'],
                        mode='markers',
                        marker=dict(symbol='triangle-down', size=10, color='red'),
                        name='卖出信号'
                    )
                )

        fig.update_layout(
            title="交易信号分析",
            xaxis_title="时间",
            yaxis_title="价格",
            template="plotly_white",
            height=500
        )

        st.plotly_chart(fig, use_container_width=True)
